package com.zxjbyte.yiyi.framework.common.util;

import cn.hutool.core.io.IoUtil;
import cn.hutool.core.net.URLEncodeUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelReader;
import com.alibaba.excel.read.builder.ExcelReaderBuilder;
import com.alibaba.excel.read.listener.ReadListener;
import com.alibaba.excel.read.metadata.ReadSheet;
import com.alibaba.excel.write.builder.ExcelWriterBuilder;
import com.alibaba.excel.write.handler.WriteHandler;
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
import com.zxjbyte.yiyi.framework.common.constant.Constant;
import com.zxjbyte.yiyi.framework.common.core.excel.DynamicColumnWidthStrategy;
import com.zxjbyte.yiyi.framework.common.core.excel.listener.ReadExcelListener;
import com.zxjbyte.yiyi.framework.common.core.excel.processor.IReadExcelProcessor;
import lombok.SneakyThrows;
import lombok.experimental.UtilityClass;
import org.springframework.http.HttpHeaders;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.util.List;

/**
 * 基于EasyExcel
 * 官方文档：https://easyexcel.opensource.alibaba.com/docs/current/quickstart/read
 *
 * 注意：本项目在lombok.config中全局开启了lombok.accessors.chain=true
 * 在读取excel实体类上需加@Accessors(chain = false)否则会读取不到数据，因为Easy Excel底层使用了cglib 目前cglib 无法支持 @accessors(chain = true)
 * 参考资料：https://github.com/alibaba/easyexcel/issues/3235
 * @Author zhangxingjia
 * @Date 2023/3/21 20:11
 * @Version: 1.0
 */
@UtilityClass
public class EasyExcelUtil {



    /**
     * 读取Excel
     * @param input
     * @param sheet
     * @param headRowNumber
     * @param hand
     * @param <T>
     * @return
     */
    public <T> List<T> read(InputStream input, int sheet, int headRowNumber, Class<T> hand) {
        ExcelReaderBuilder excelReaderBuilder = getExcelReaderBuilder(IoUtil.toBuffered(input), hand, null);
        return excelReaderBuilder.sheet(sheet).headRowNumber(headRowNumber).doReadSync();
    }

    /**
     * 读取Excel
     * @param input
     * @param hand
     * @param <T>
     * @return
     */
    public <T> List<T> read(InputStream input, Class<T> hand) {
        return read(input, 0, 1, hand);
    }

    /**
     * 读取Excel的指定sheet数据
     * @param file
     * @param sheet 从0开始
     * @param hand
     * @param <T>
     * @return
     */
    public <T> List<T> read(MultipartFile file, int sheet, Class<T> hand) {
        return read(file, sheet, 1, hand);
    }

    /**
     * 读取Excel的指定sheet，指定头所在的行
     * @param file excel文件
     * @param sheet sheet序号(从0开始)
     * @param headRowNumber 默认1
     * @param hand
     * @param <T>
     * @return
     */
    public <T> List<T> read(MultipartFile file, int sheet, int headRowNumber, Class<T> hand) {
        ExcelReaderBuilder excelReaderBuilder = getExcelReaderBuilder(file, hand, null);
        excelReaderBuilder.sheet(sheet).headRowNumber(headRowNumber).doRead();
        return excelReaderBuilder.autoCloseStream(Boolean.FALSE) // 不要自动关闭，交给 Servlet 自己处理
                .doReadAllSync();
    }

    /**
     * 同步返回所有数据，注意此方法会把读取到的数据缓存到内存中
     * @param file
     * @param hand
     * @param <T>
     * @return
     */
    public <T> List<T> read(MultipartFile file, Class<T> hand) {
        return read(file, 0, 1, hand);
    }

    /**
     * 读取并处理数据
     * @param file
     * @param head
     * @param readExcelProcessor
     * @param <T>
     */
    public <T> void read(MultipartFile file, Class<T> head, IReadExcelProcessor<T> readExcelProcessor) {
        //ReadExcelListener 不能被spring管理，要每次读取excel都要new,然后里面用到spring可以构造方法传进去
        ReadExcelListener<T> readExcelListener = new ReadExcelListener<>(readExcelProcessor);
        ExcelReaderBuilder excelReaderBuilder = getExcelReaderBuilder(file, head, readExcelListener);
        excelReaderBuilder.autoCloseStream(Boolean.FALSE)
                .doReadAll();
    }

    /**
     * 不创建对象的读
     *
     * @param input
     * @param headRowNumber
     * @param readExcelProcessor
     * @return
     */
    public <T> void noModelRead(InputStream input, Integer headRowNumber, IReadExcelProcessor<T> readExcelProcessor) {
        noModelRead(input, null, headRowNumber, readExcelProcessor);
    }

    /**
     * 不创建对象的读取指定sheet页
     *
     * @param input
     * @param headRowNumber 表头所在行
     * @param readExcelProcessor
     * @return
     */
    public <T> void noModelReadSheet(InputStream input, Integer sheetNo, Integer headRowNumber, IReadExcelProcessor<T> readExcelProcessor) {
        ExcelReader excelReader = EasyExcel.read(input).build();
        ReadSheet readSheet = EasyExcel.readSheet(sheetNo).headRowNumber(headRowNumber).registerReadListener(new ReadExcelListener<>(readExcelProcessor)).build();
        excelReader.read(readSheet);
    }

    /**
     * 不创建对象的读
     *
     * @param input
     * @param head
     * @param headRowNumber 默认不传为1
     * @param readExcelProcessor
     * @return
     */
    public <T> void noModelRead(InputStream input, List<List<String>> head, Integer headRowNumber,
                                IReadExcelProcessor<T> readExcelProcessor) {
        //ReadExcelListener 不能被spring管理，要每次读取excel都要new,然后里面用到spring可以构造方法传进去
        ReadExcelListener<T> readExcelListener = new ReadExcelListener<>(readExcelProcessor);
        ExcelReaderBuilder excelReaderBuilder = getExcelReaderBuilder(input, head, readExcelListener);
        if(headRowNumber != null){
            excelReaderBuilder.headRowNumber(headRowNumber);
        }
        excelReaderBuilder.autoCloseStream(Boolean.FALSE)
                .doReadAll();
    }


    /**
     * 将列表以Excel写出到response
     *
     * @param response response
     * @param filename 需要带扩展名 例如：测试.xlsx
     * @param sheetName 工作簿名称
     * @param head  表头类
     * @param data 数据
     * @param writeHandler 自定义write handler
     * @param <T>
     */
    @SneakyThrows
    public <T> void writeWeb(HttpServletResponse response, String filename, String sheetName,
                                 Class<T> head, List<T> data, WriteHandler... writeHandler) {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        ExcelWriterBuilder builder = EasyExcel.write(outputStream, head)
                .autoCloseStream(Boolean.FALSE);
        setWriteHandler(builder, writeHandler);
        builder.sheet(sheetName).doWrite(data);
        setResponse(response, filename, outputStream.toByteArray());
    }


    /**
     * 不创建对象动态的写出到response
     *
     * @param response response
     * @param filename 需要带扩展名 例如：测试.xlsx
     * @param sheetName 工作簿名称
     * @param head 动态表头
     * @param data 数据
     * @param writeHandler 自定义write handler
     * @param <T>
     */
    @SneakyThrows
    public <T> void noModelWriteWeb(HttpServletResponse response, String filename, String sheetName,
                                    List<List<String>> head, List<T> data, WriteHandler... writeHandler) {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        ExcelWriterBuilder builder = EasyExcel.write(outputStream)
                .head(head)
                .autoCloseStream(Boolean.FALSE);
        setWriteHandler(builder, writeHandler);
        builder.sheet(sheetName).doWrite(data);
        setResponse(response, filename, outputStream.toByteArray());
    }

    /**
     * 不创建对象动态的写出到文件
     * @param pathName 需要带扩展名 例如：测试.xlsx
     * @param sheetName 工作簿名称
     * @param head 动态表头
     * @param data 数据
     * @param writeHandler 自定义write handler
     * @param <T>
     */
    @SneakyThrows
    public <T> void noModelWriteFile(String pathName, String sheetName, List<List<String>> head, List<T> data,
                                     WriteHandler... writeHandler) {
        ExcelWriterBuilder builder = EasyExcel.write(pathName).head(head);
        setWriteHandler(builder, writeHandler);
        builder.sheet(sheetName).doWrite(data);
    }

    private void setWriteHandler(ExcelWriterBuilder builder, WriteHandler[] writeHandler){
        if(writeHandler != null && writeHandler.length > 0){
            for(WriteHandler handler : writeHandler){
                builder.registerWriteHandler(handler);
            }
        } else {
            builder.registerWriteHandler(new DynamicColumnWidthStrategy());
        }
    }

    @SneakyThrows
    private void setResponse(HttpServletResponse response, String filename, byte[] content){
        // 请求头 Content-Disposition 指示浏览器如何处理接收到的响应体 attachment: 将响应体作为附件下载并指定文件名， 内容写法 符合RFC 5987规范标准
        response.addHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename*=utf-8''" + URLEncodeUtil.encode(filename));
        if(StrUtil.endWithIgnoreCase(filename, ".xls")){
            // 早期版本的Excel文件格式的MIME类型, 1997年发布的Excel 97中被引入, 扩展名是.xls
            response.setContentType("application/vnd.ms-excel");
        } else {
            // 较新的Excel文件格式的MIME类型，2007中被引入，使用XML和ZIP压缩来存储数据, 这种格式的扩展名是.xlsx
            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        }
        response.setContentLength(content.length);
        response.setCharacterEncoding(Constant.UTF_8);
        response.getOutputStream().write(content);
        response.getOutputStream().flush();
    }



    /**
     * 获取构建类
     *
     * @param excel        excel文件
     * @param readListener excel监听类
     * @return ExcelReaderBuilder
     */
    @SneakyThrows
    private  <T> ExcelReaderBuilder getExcelReaderBuilder(MultipartFile excel, Class<T> head, ReadListener<T> readListener) {
        String filename = excel.getOriginalFilename();
        ValidationUtil.assertNotBlank(filename, "请上传文件", "");
        ValidationUtil.assertFalse((!StrUtil.endWithIgnoreCase(filename, ".xls") && !StrUtil.endWithIgnoreCase(filename, ".xlsx")), "请上传以\".xls\"或者\".xlsx\"为文件扩展名结尾的Excel文件");
        return getExcelReaderBuilder(IoUtil.toBuffered(excel.getInputStream()), head, readListener);
    }

    private <T> ExcelReaderBuilder getExcelReaderBuilder(InputStream input, Class<T> head, ReadListener<T> readListener){
        return EasyExcel.read(input, head, readListener);
    }

    private <T> ExcelReaderBuilder getExcelReaderBuilder(InputStream input, List<List<String>> head, ReadListener<T> readListener){
        ExcelReaderBuilder builder = EasyExcel.read(input, readListener);
        if(head != null){
            builder.head(head);
        }
        return builder;
    }
}
